#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Purpose: Network management facility functions
# Created: 07/13/2009
# $Id $
# Copyright (c) 2008,2009 by University College London
# Authors: Didrik Pinte <dpinte@dipole-consulting.com>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with HydroPlatform for details.
"""
Facility functions related to the management of a network and project object
"""

import logging

from sqlalchemy import and_, or_

from hydroplatform.model.network import NodeType, LinkType, Node, Link, \
     LinkValue, NodeValue, Network
from hydroplatform.model.units import Unit
from hydroplatform.model.fields import TimeSerie, SeasonalParameter

def copy_objecttype_as(session, project, objecttype, name=None):
    """
    Copy an objecttype to another project. 
    """
    if name is None:
        name = objecttype.name
        
    if isinstance(objecttype, NodeType):
        new_otype = NodeType(name, objecttype.description,
                            project, 
                            objecttype.shape,
                            objecttype.pen,
                            objecttype.brush,
                            objecttype.size)
    elif isinstance(objecttype, LinkType):
        new_otype = LinkType(name, objecttype.description,
                            project, 
                            objecttype.pen,
                            objecttype.style,
                            objecttype.size)
    session.add(new_otype)
    for field in objecttype.fields:
        newfield = field.copy_to_otype(new_otype)
        session.add(newfield)
    session.commit()
    
def copy_objecttype_toproject(session, to_project, objecttype):
    """
    Copy objecttype to to_project (copies object type and fields)
    
    Parameters
    ----------
    session : sqlalchemy Session
        The db session used to copy the object to
    to_project : Project
        Destination project instance
    objecttype : LinkType or Nodetype
        Object type to be transfered.
        
    Returns
    -------
    id : int
        Id of the new object type
    """
    # copy node type to project
    newtype = objecttype.copy_to_project(to_project)
    session.add(newtype)
    session.flush()
    for field in objecttype.fields:
        newfield = field.copy_to_otype(newtype)
        session.add(newfield)
    return newtype.id
        
def copy_object_toproject(session, to_project, nobject):
    """
    Copy nobject to to_project (copies object and values)
   
    Parameters
    ----------
    session : sqlalchemy Session
        The db session used to copy the object to
    to_project : Project
        Destination project instance
    nobject : Link or Node
        Object to be transfered.
        
    Returns
    -------
    id : int
        Id of the new object
    """
    # copy node type to project
    newobject = nobject.copy_to_project(to_project)
    session.add(newobject)
    for value in nobject.values:
        newvalue = value.copy_to_object(newobject)
        session.add(newvalue)
    return newobject.id

def copy_network_toproject(session, to_project, network, mapping):
    """
    Copy network to to_project without copying the node and links (they must 
    be transferred to the project before). it uses a mapping_dict to get a 
    mapping between the id's from the input project to the output project
   
    Parameters
    ----------
    session : sqlalchemy Session
        The db session used to copy the object to
    to_project : Project
        Destination project instance
    network : Network
        Network to be transfered.
    mapping : dict
        Dictionnary with key as id from this project and value key in the 
        destination project
        
    Returns
    -------
    network_id : int
        Id of the new network
    """
    logging.warn("Transferring network is not implemented")
    dest_network =  Network(network.name, to_project)
    session.add(dest_network)
    session.flush()
    # FIXME : are we missing the node types ?
    # transfer nodes
    for node in network.nodes:
        dest_network.nodes.append(session.query(Node).filter(Node.id == mapping['nodes'][node.id]).first())
    # transfer links
    for link in network.links:
        dest_network.links.append(session.query(Link).filter(Link.id == mapping['links'][link.id]).first())
    session.commit()
    session.refresh(dest_network)
    return dest_network.id


def save_network_as(session, network, new_name):
    """
    Saves the given network as a new network
    """
    dest_network = Network(new_name, network.project)
    session.add(dest_network)
    session.flush()
    # transfer nodes and links
    for node in network.nodes:
        dest_network.nodes.append(node)
    # transfer links
    for link in network.links:
        dest_network.links.append(link)
    session.commit()
    session.refresh(dest_network)
    return dest_network

def project_time_horizon_changed(project, dbsession):
    """
    When the project time horizon is changed, the time series and other 
    seasonal fields must be updated accordingly
    
    For each nodetype and linktype, update the fields that are dependant
    on the project time horizon
    
    For each node and link, check the values associated to these fields
    """
    
    for nodetype in project.nodetypes:
        for field in nodetype.fields:
            fdat = field.attribute_type
            if isinstance(fdat, (TimeSerie, SeasonalParameter)):
                logging.debug(fdat.name)
                fdat.update_time_horizon(project)
                # select all the nodes with a value for this field
                node_values = dbsession.query(NodeValue).filter(NodeValue.attribute_id == field.id)
                if node_values is not None:
                    for nvalue in node_values :
                        # update values
                        data = nvalue.value
                        updated_data = fdat.reshape_data(data)
                        if updated_data is not None:
                            nvalue.value = updated_data
    for linktype in project.linktypes:
        for field in linktype.fields:
            logging.debug(fdat.name)
            fdat = field.attribute_type
            if isinstance(fdat, (TimeSerie, SeasonalParameter)):
                fdat.update_time_horizon(project)
                # select all the nodes with a value for this field
                node_values = dbsession.query(NodeValue).filter(NodeValue.attribute_id == field.id)
                if node_values is not None:
                    for nvalue in node_values :
                        # update values
                        data = nvalue.value
                        updated_data = fdat.reshape_data(data)
                        if updated_data is not None:
                            nvalue.value = updated_data
    
    
def delete_project(dbsession, project):
    """
    Delete a project and all the dependant objects
    """
    for node in project.nodes:
        delete_from_project(dbsession, project, node)
    dbsession.delete(project)
    dbsession.commit()
    project = None

def delete_network(dbsession, network):
    """
    Delete a network and all the dependant objects
    """
    for node in network.nodes:
        delete_node_from_network(dbsession, network, node)
    dbsession.delete(network)
    dbsession.commit()
    
def delete_objecttype_from_project(dbsession, project, object_type):
    """
    Delete an object type from the project and all its associated object
    """
    
    if isinstance(object_type, NodeType):
        for node in object_type.nodes:
            delete_from_project(dbsession, project, node)
    elif isinstance(object_type, LinkType):
        for link in object_type.links:
            delete_from_project(dbsession, project, link)
    dbsession.delete(object_type)
    dbsession.commit()
    
def delete_from_project(dbsession, project, nobject):
    '''
    Delete an object from the project
    '''
    # delete the network object form the project too ? 
    logging.debug('Deleting from project')
    if isinstance(nobject, Node):
        # delete all of the fields attribute of the node
        dbsession.query(NodeValue).filter( \
            NodeValue.node_id == nobject.id).delete()
        # remove node from the networks it belongs to
        for network in project.networks:
            delete_node_from_network(dbsession, network, nobject)
        links = dbsession.query(Link).filter(       \
                or_(Link.start_node_id == nobject.id,  \
                    Link.end_node_id   == nobject.id), \
                ).all()
        for link in links:
            # delete all the values belonging to the link
            dbsession.query(LinkValue).filter( \
                LinkValue.link_id == link.id).delete() 
            # delete the link
            dbsession.delete(link)
        project.nodes.remove(nobject)
    elif isinstance(nobject, Link):
        # delete all of the fields attribute of the link
        dbsession.query(LinkValue).filter( \
            LinkValue.link_id == nobject.id).delete() 
        # remove link from the networks it belongs to
        for network in project.networks:
            network.remove_link(nobject)
        # remove the link from the project
        project.links.remove(nobject)

            
    dbsession.delete(nobject)
    dbsession.commit()
    dbsession.refresh(project)            
        
def delete_node_from_network(dbsession, network, node):
    """
    Delete a node and its links from given network
    """
    logging.debug('Deleting from network %s' % network.name)
    # remove all the links using this node in this network        
    links = dbsession.query(Link).filter(
        and_(
            or_(Link.start_node_id == node.id, 
                Link.end_node_id   == node.id),
        )).all()
    for link in links : 
        try:
            network.remove_link(link)
        except ValueError:
            logging.info(
                "ValueError while deleting links with node %s" % \
                node) 
    # delete the node                    
    network.remove_node(node)
    
    
def check_project_has_unit(session, project, unit, add_if_missing=True):
    """
    Check that a project has the given unit/units and add them if needed
    """
    import types
    if isinstance(unit, types.ListType):
        units = unit
    else :
        units = [unit]
    
    has_units = True
    for unit in units:
        for existing_unit in project.units:
            if existing_unit is not None and \
               existing_unit.name == unit :
                break
        else:
            if add_if_missing: 
                # load unit with this name from db and add it to the project
                dbunit = session.query(Unit).filter(Unit.name == unit).first()
                project.units.append(dbunit)
            else:
                has_units = False 
    return has_units
                